package cn.ywyself.extend.aop.validate;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.jfinal.kit.Ret;
import com.jfinal.kit.StrKit;

import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 检查方法上是否有验证注解，并根据注解值对数据进行验证
 *
 * @Author: ywyself
 * @Created: 2018-10-09 10:59
 */
public class PreInterceptor implements Interceptor {

    private Controller controller;
    private boolean invalid;
    private Ret errData;

    @Override
    public void intercept(Invocation inv) {
        controller = inv.getController();
        invalid = false;
        errData = Ret.create();
        // 获取方法名
        Method method = inv.getMethod();
        // 获取方法上的注解
        PreValidators preValidators = method.getAnnotation(PreValidators.class);
        PreValidator preValidator = method.getAnnotation(PreValidator.class);
        if (preValidators == null && preValidator == null) {
            inv.invoke();
            return;
        }
        List<PreValidator> validatorList = new ArrayList<>();
        if (preValidator != null) {
            validatorList.add(preValidator);
        }
        if (preValidators != null) {
            validatorList.addAll(Arrays.asList(preValidators.value()));
        }
        try {
            for (PreValidator vd : validatorList) {
                validate(vd);
            }
        } catch (PreValidatorException ignored) {
        }

        if (invalid) {
            controller.renderJson(errData);
        } else {
            inv.invoke();
        }
    }

    private void validate(PreValidator vd) throws PreValidatorException {
        switch (vd.dataType()) {
            case STRING:
                requiredString(vd);
                break;
            case INTEGER:
                requiredInteger(vd);
                break;
            case DOUBLE:
                requiredDouble(vd);
                break;
            case URL:
                requiredUrl(vd);
                break;
            case EMAIL:
                requiredEmail(vd);
                break;
            case TEL:
                requiredTel(vd);
                break;
            case QQ:
                requiredQQ(vd);
                break;
            case NAME:
                requiredName(vd);
                break;
            case BANK_NO:
                requiredBankNo(vd);
                break;
            case ID_CARD:
                requiredIDCard(vd);
                break;
            default:
                break;
        }
    }

    /**
     * 添加错误
     */
    private void addError(String errMsg, String errField) throws PreValidatorException {
        invalid = true;
        errData = Ret.fail().set("errMsg", errMsg).set("errField", errField);
        throw new PreValidatorException();
    }

    /**
     * 域不能为空
     */
    private void notBlank(PreValidator vd) throws PreValidatorException {
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
    }

    /**
     * 验证字符串
     */
    private void requiredString(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (vd.min() != PreConst.DISMISS && value.length() < vd.min()) {
            addError(vd.desc() + "最少为" + vd.min() + "个字", vd.field());
        }
        if (vd.max() != PreConst.DISMISS && value.length() > vd.max()) {
            addError(vd.desc() + "最多为" + vd.max() + "个字", vd.field());
        }
    }

    private void requiredInteger(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        int temp;
        try {
            temp = Integer.parseInt(value.trim());
        } catch (Exception e) {
            addError(vd.desc() + "必须为整数", vd.field());
            return;
        }
        if (vd.min() != PreConst.DISMISS && temp < vd.min()) {
            addError(vd.desc() + "不能小于" + (int) (vd.min()), vd.field());
        }
        if (vd.max() != PreConst.DISMISS && temp > vd.max()) {
            addError(vd.desc() + "不能大于" + (int) (vd.max()), vd.field());
        }
    }

    private void requiredDouble(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        double temp;
        try {
            temp = Double.parseDouble(value.trim());
        } catch (Exception e) {
            addError("必须为合法小数", vd.field());
            return;
        }
        if (vd.min() != PreConst.DISMISS && temp < vd.min()) {
            addError("最小为值" + vd.min(), vd.field());
        }
        if (vd.max() != PreConst.DISMISS && temp > vd.max()) {
            addError("最大为值" + vd.max(), vd.field());
        }
    }

    private void requiredUrl(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        try {
            value = value.trim();
            if (value.startsWith("https://")) {
                // URL doesn't understand the https protocol, hack it
                value = "http://" + value.substring(8);
            }
            new URL(value);
        } catch (MalformedURLException e) {
            addError(vd.desc() + "不合法", vd.field());
        }
    }

    /**
     * 邮箱格式检查
     */
    private static final Pattern EMAIL_PATTERN = Pattern.compile("\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}");

    private void requiredEmail(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!EMAIL_PATTERN.matcher(value).matches()) {
            addError(vd.desc() + "格式错误", vd.field());
        }
    }

    /**
     * 手机号格式检查
     */
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[0-9]\\d{4,10}$");

    private void requiredTel(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!PHONE_PATTERN.matcher(value).matches()) {
            addError(vd.desc() + "格式错误", vd.field());
        }
    }

    /**
     * 对qq号进行验证，要求5~15位，不能以0开头，只能是数字
     */
    private static final Pattern QQ_PATTERN = Pattern.compile("[1-9][0-9]{4,14}");

    private void requiredQQ(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!QQ_PATTERN.matcher(value).matches()) {
            addError(vd.desc() + "格式错误", vd.field());
        }
    }

    /**
     * 姓名检查，支持少数民族的人名，或者外国人的中译名[需要考虑支持少数民族的人名，或者外国人的中译名]
     */
    private static final Pattern NAME_PATTERN = Pattern.compile("^[\\u4E00-\\u9FA5]{2,5}(?:·[\\u4E00-\\u9FA5]{2,5})*$");

    private void requiredName(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!NAME_PATTERN.matcher(value).matches()) {
            addError(vd.desc() + "不合法", vd.field());
        }
    }

    /**
     * 银行卡校验
     */
    private static final Pattern CARD_PATTERN = Pattern.compile("\\d{15,20}");

    private void requiredBankNo(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!CARD_PATTERN.matcher(value).matches()) {
            addError(vd.desc() + "不正确", vd.field());
        }
    }

    private void requiredIDCard(PreValidator vd) throws PreValidatorException {
        notBlank(vd);
        String value = controller.getPara(vd.field());
        if (StrKit.isBlank(value)) {
            addError(vd.desc() + "不能为空", vd.field());
        }
        if (!isIDCard(value)) {
            addError(vd.desc() + "不正确", vd.field());
        }
    }

    /**
     * 检查号码是否符合规范，包括长度，类型
     */
    private static final Pattern ID_CARD = Pattern.compile("(^\\d{15}$)|(^\\d{17}(\\d|X)$)");

    /**
     * 身份证15位时，次序为省（3位）市（3位）年（2位）月（2位）日（2位）校验位（3位），皆为数字
     */
    private static final Pattern ID_CARD_15 = Pattern.compile("^(\\d{6})(\\d{2})(\\d{2})(\\d{2})(\\d{3})$");

    /**
     * 身份证18位时，次序为省（3位）市（3位）年（4位）月（2位）日（2位）校验位（4位），校验位末尾可能为X
     */
    private static final Pattern ID_CARD_18 = Pattern.compile("^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|X)$");

    /**
     * 身份证号验证
     *
     * @param str 身份证号
     * @return true|合法
     */
    private static boolean isIDCard(String str) {
        if (!ID_CARD.matcher(str).matches()) {
            // 身份证号码为15位或者18位，15位时全为数字，18位前17位为数字，最后一位是校验位，可能为数字或字符X
            return false;
        }
        // 取身份证前两位,校验省份
        Map<String, String> city = new HashMap<String, String>();
        city.put("11", "北京");
        city.put("12", "天津");
        city.put("13", "河北");
        city.put("14", "山西");
        city.put("15", "内蒙古");
        city.put("21", "辽宁");
        city.put("22", "吉林");
        city.put("23", "黑龙江");
        city.put("31", "上海");
        city.put("32", "江苏");
        city.put("33", "浙江");
        city.put("34", "安徽");
        city.put("35", "福建");
        city.put("36", "江西");
        city.put("37", "山东");
        city.put("41", "河南");
        city.put("42", "湖北");
        city.put("43", "湖南");
        city.put("44", "广东");
        city.put("45", "广西");
        city.put("46", "海南");
        city.put("50", "重庆");
        city.put("51", "四川");
        city.put("52", "贵州");
        city.put("53", "云南");
        city.put("54", "西藏");
        city.put("61", "陕西");
        city.put("62", "甘肃");
        city.put("63", "青海");
        city.put("64", "宁夏");
        city.put("65", "新疆");
        city.put("71", "台湾");
        city.put("81", "香港");
        city.put("82", "澳门");
        city.put("91", "国外");
        // 取身份证前两位,校验省份
        String cityKey = str.substring(0, 2);
        if (!city.containsKey(cityKey)) {
            return false;
        }
        if (str.length() == 15) {
            if (ID_CARD_15.matcher(str).matches()) {
                // 检查生日是否正确
                String birthday = "19" + str.substring(6, 12);
                if (!isBirthday(birthday)) {
                    return false;
                }
            } else {
                return false;
            }
        } else if (str.length() == 18) {
            if (ID_CARD_18.matcher(str).matches()) {
                // 检查生日是否正确
                String birthday = str.substring(6, 14);
                if (!isBirthday(birthday)) {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
        // 检验位的检测
        // 15位转18位
        str = changeFivteenToEighteen(str);
        if (str.length() != 18) {
            return false;
        }
        int[] arrInt = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        char[] arrCh = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
        int cardTemp = 0;
        for (int i = 0; i < 17; i++) {
            cardTemp += Integer.parseInt(str.substring(i, i + 1)) * arrInt[i];
        }
        return arrCh[cardTemp % 11] == str.substring(17, 18).charAt(0);
    }

    /**
     * 检查生日是否合法
     *
     * @param str 格式，如：19930101
     * @return true|合法
     */
    private static boolean isBirthday(String str) {
        if (str.length() != 8) {
            return false;
        }
        int y = Integer.parseInt(str.substring(0, 4));
        int m = Integer.parseInt(str.substring(4, 6));
        int d = Integer.parseInt(str.substring(6, 8));
        // 获取当前r
        Calendar c = Calendar.getInstance();
        int nowYear = c.get(Calendar.YEAR);
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            date = sdf.parse(str);
        } catch (ParseException e) {
            return false;
        }
        c.setTime(date);
        if (c.get(Calendar.YEAR) != y || c.get(Calendar.MONTH) + 1 != m || c.get(Calendar.DAY_OF_MONTH) != d) {
            // 月或者日不是合法的范围
            return false;
        }
        // 年
        int per = nowYear - y;
        return per >= 3 && per <= 130;
    }

    /**
     * 15位身份证转18位身份证，传入18位不进行转换
     *
     * @param str 15位身份证
     * @return 18位身份证
     */
    private static String changeFivteenToEighteen(String str) {
        if (str.length() != 15) {
            return str;
        }
        int[] arrInt = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        char[] arrCh = new char[]{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
        int cardTemp = 0;
        str = str.substring(0, 6) + "19" + str.substring(6, str.length() - 6);
        for (int i = 0; i < 17; i++) {
            cardTemp += Integer.parseInt(str.substring(i, i + 1)) * arrInt[i];
        }
        return str + arrCh[cardTemp % 11];
    }
}
